Day4的時候有提過,tool use的原本的設計目的,是由模型自己決定要呼叫哪個工具、拿到結果之後再決定下一步,形成一個迴圈。今天正式進入Part4,分享迴圈是如何運作,以及如何控制迴圈不失控。
拿掉所有細節,一個最單純的迴圈是這樣運作的:
先看最小的版本——只跑一輪,把機制看清楚:
import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
weather_tool = {
"name": "get_weather",
"description": "查詢指定城市目前的天氣",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string", "description": "城市名稱"}},
"required": ["city"],
},
}
def get_weather(city):
return f"{city} 目前是晴天,氣溫 28 度"
messages = [{"role": "user", "content": "台北現在天氣如何?"}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
if response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = get_weather(tool_use.input["city"])
messages.append({"role": "assistant", "content": response.content})
messages.append(
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": tool_use.id, "content": result}
],
}
)
final_response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[weather_tool],
messages=messages,
)
print(final_response.content[0].text)
else:
print(response.content[0].text)
最關鍵的判斷點是 response.stop_reason,模型不是自己「跑」去執行工具,而是回傳一個訊號,說明「想呼叫這個工具、帶著這些參數」;實際執行工具、把結果餵回去,全部是程式的責任。模型負責決策,程式負責執行與串接。
實際場景常常不只呼叫一次——模型可能需要連續呼叫好幾次工具,中間互相參考彼此的結果,才能真正把一件事情辦完。把 stop_reason == "tool_use" 的判斷放進 while 迴圈裡,就能讓模型持續呼叫工具,直到它自己覺得資訊夠了為止:
compare_tool = {
"name": "compare_temperature",
"description": "比較兩個溫度數字,回傳哪個比較高",
"input_schema": {
"type": "object",
"properties": {
"temp_a": {"type": "number"},
"temp_b": {"type": "number"},
},
"required": ["temp_a", "temp_b"],
},
}
def compare_temperature(temp_a, temp_b):
return f"{'第一個' if temp_a > temp_b else '第二個'}比較高"
tools = {"get_weather": get_weather, "compare_temperature": compare_temperature}
tool_defs = [weather_tool, compare_tool]
messages = [{"role": "user", "content": "台北跟東京,現在哪裡比較熱?"}]
while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=tool_defs,
messages=messages,
)
if response.stop_reason != "tool_use":
final_text = next(b.text for b in response.content if b.type == "text")
print(final_text)
break
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
fn = tools[block.name]
result = fn(**block.input)
tool_results.append(
{"type": "tool_result", "tool_use_id": block.id, "content": str(result)}
)
messages.append({"role": "user", "content": tool_results})
跑起來大概是這樣:模型先呼叫 get_weather 查台北的溫度,把結果餵回去;模型可能接著再呼叫一次 get_weather 查東京的溫度;有了兩個城市的溫度之後,模型再呼叫 compare_temperature 做比較;最後才用文字回答「台北比較熱」。整個過程模型呼叫了三次工具,程式完全不用寫死「先查台北、再查東京、再比較」這個順序——是模型自己一步步決定要做什麼。
兩個容易忽略的細節:一次回應可能同時呼叫多個工具(上面用 for block in response.content 把所有 tool_use 都跑過一輪),所有 tool_result 也要包在同一個 user 訊息裡送回去;另外,assistant 的原始回應要存回歷史,模型需要看到自己前一輪說了什麼、呼叫了什麼工具,才能接著決定下一步。
這個迴圈目前是假設工具永遠執行成功、假設模型不會沒完沒了地一直呼叫下去。實務上工具執行可能失敗(可以把失敗結果包成 is_error: true 的 tool_result 送回去,讓模型知道剛才那次沒成功),迴圈也需要一個最大次數上限,避免真的失控地跑下去。這些是讓迴圈「跑得動、跑得完」的必要工程。
今天建立了工具迴圈的概念:模型呼叫工具、拿到結果、決定下一步,形成一個迴圈;stop_reason 是模型跟這層機制溝通「還要不要繼續」的訊號。
不過,光是讓迴圈能跑,離「讓 agent 真正可靠」還有一段距離。同一種錯誤,會不會下次又用一模一樣的方式發生一次?明天要從這個問題出發,介紹一個今年才被明確提出,但已經在實務上備受重視的概念:Harness Engineering。